class FractalFinder {
  TrackStats stats;
  float sclaeFactor;
  float measureSize;


  FractalFinder(TrackStats stats) {
    this.stats = stats;
  }

  ArrayList<PVector> calcNotesPoints() {
    ArrayList<PVector> list = new ArrayList<PVector>(); 
    for (int i = 0; i < stats.notes.size(); i++) {
      list.add(new PVector(i*10, stats.notes.get(i)*5));
    }
    return list;
  }

  ArrayList<PVector> calcVelocityesPoints() {
    ArrayList<PVector> list = new ArrayList<PVector>(); 
    for (int i = 0; i < stats.velocityes.size(); i++) {
      list.add(new PVector(i*10, stats.velocityes.get(i)*5));
    }
    return list;
  }

  ArrayList<PVector> calcDurationsPoints() {
    ArrayList<PVector> list = new ArrayList<PVector>(); 
    for (int i = 0; i < stats.durations.size(); i++) {
      list.add(new PVector(i*10, getDurationNum(stats.durations.get(i))*5));
    }
    return list;
  }

  ArrayList<PVector> calcPointNotes(ArrayList<PVector> points, int position) {
    ArrayList<PVector> list = new ArrayList<PVector>(); 
    for (int i = position; i < points.size(); i++) {
      PVector point = points.get(i);
      list.add(new PVector(point.x/10, point.y/5));
    }
    return list;
  }

  ArrayList<PVector> measureCurve(ArrayList<PVector> points) {
    ArrayList<PVector> lengths = new ArrayList<PVector>();
    for (float rulerSize = 5; rulerSize < 10; rulerSize++) {
      float perimeter = 0;
      for (int i = 0; i < points.size()-1; i++) {
        float distance = dist(points.get(i).x, points.get(i).y, points.get(i+1).x, points.get(i+1).y);
        float step = (float)ceil(distance/rulerSize);
        if (step < 0) step = 1.0;
        perimeter += step;
      }
      lengths.add(new PVector(perimeter, 1/rulerSize));
    }
    return lengths;
  }


  ArrayList<PVector> calcLogParams(ArrayList<PVector> params) {
    ArrayList<PVector> logStats = new ArrayList<PVector>();
    for (int i = 0; i < params.size(); i++) {
      float logMeasure =(float) Math.log10(params.get(i).x);
      float logScale =(float) Math.log10(params.get(i).y);

      logStats.add(new PVector(logMeasure, logScale));
    }

    return logStats;
  }

  float calcSlope(ArrayList<PVector> params) {
    double a = params.get(0).x-params.get(params.size()-1).x;
    double b = params.get(0).y-params.get(params.size()-1).y;

    return Float.valueOf(String.format("%.3f", 1+(a/b)));
  }


  ArrayList<PVector> nextNoteRandom(ArrayList<PVector> params, float aimDimension) {
    boolean noteFits = false;
    ArrayList<PVector> newNotesList = new ArrayList<PVector>();
    long seed = rnd.nextLong();
    setupFbm(stats.getNotesSD(), params.size());
    fbm(2-aimDimension, seed);
    int i = 0;
    float avg = avg(stats.notes);
    while (!noteFits) {
      newNotesList = params;
      float possibleNote = constrain(avg + round(X[i]), stats.notesRange.x, stats.notesRange.y);
      newNotesList.add(new PVector(newNotesList.size()*10, possibleNote*5));

      float slope = calcSlope(calcLogParams(measureCurve(newNotesList)));
      if (slope == aimDimension) {
        //println("note");
        noteFits = true;
      }
      else {
        newNotesList.remove(newNotesList.size()-1);
      }
      if (i < X.length-1) {
        i++;
      }
      else {
        //    println("new seed for notes");
        i = 0;
        seed = rnd.nextLong();
        fbm(2-aimDimension, seed);
      }
    }   
    return newNotesList;
  }

  ArrayList<PVector> nextDurationRandom(ArrayList<PVector> params, float aimDimension) {
    boolean noteFits = false;
    ArrayList<PVector> newNotesList = new ArrayList<PVector>();
    long seed = rnd.nextLong();
    setupFbm(stats.getDurationsSD(), params.size());

    fbm(2-aimDimension, seed);
    int i = 0;
    float avg = avgDur(stats.durations);
    while (!noteFits) {
      newNotesList = params;
      float possibleNote = constrain(avg + round(X[i]*42), stats.durationRange.x, stats.durationRange.y);//round(map(X[i], -0.024, 0.054, 1, 7)); //constrain(round(X[i]), stats.durationRange.x, stats.durationRange.y);
      newNotesList.add(new PVector(newNotesList.size()*10, possibleNote*5));

      float slope = calcSlope(calcLogParams(measureCurve(newNotesList)));
      if (slope == aimDimension || true) {
        noteFits = true;
      }
      else {
        newNotesList.remove(newNotesList.size()-1);
      }
      if (i < X.length-1) {
        i++;
      }
      else {
        i = 0;
        seed = rnd.nextLong();
        fbm(2-aimDimension, seed);
      }
    }   
    return newNotesList;
  }

  ArrayList<PVector> nextVelocityRandom(ArrayList<PVector> params, float aimDimension) {
    boolean noteFits = false;
    ArrayList<PVector> newNotesList = new ArrayList<PVector>();
    long seed = rnd.nextLong();
    setupFbm(stats.getVelocityesSD(), params.size());
    fbm(2-aimDimension, seed);
    int i = 0;
    float avg = avg(stats.velocityes);
    while (!noteFits) {
      newNotesList = params;
      float possibleNote =  constrain(avg + round(X[i]), stats.velocityRange.x, stats.velocityRange.y); // constrain(round(X[i]) + (stats.velocityRange.x + stats.velocityRange.y)/2, 0, 127);
      newNotesList.add(new PVector(newNotesList.size()*10, possibleNote*5));

      float slope = calcSlope(calcLogParams(measureCurve(newNotesList)));

      if (slope == aimDimension) {
        //println("velocity");
        noteFits = true;
      }
      else {
        newNotesList.remove(newNotesList.size()-1);
      }
      if (i < X.length-1) {
        i++;
      }
      else {
        i = 0;
        seed = rnd.nextLong();
        fbm(2-aimDimension, seed);
      }
    }   
    return newNotesList;
  }
}

float uberRandom(PVector constrains) {
  float a = floor(random(constrains.x, constrains.y));
  float b = floor(random(constrains.x, constrains.y));
  float c = floor(random(constrains.x, constrains.y));

  float val = a+b+c/3;
  if (val > 128.0) {
    val = 128.0;
  }
  return floor(val);
}

float fNoise(PVector constains, float time) {

  float n = noise(time) * constains.y;
  //println(round(n));
  return round(n);
}

float getDurationNum(float val) {
  if (val == 1/64.0) return 1.0;
  if (val == 1/32.0) return 2.0;
  if (val == 1/16.0) return 3.0;
  if (val == 1/8.0) return 4.0;
  if (val == 1/4.0) return 5.0;
  if (val == 1/2.0) return 6.0;
  if (val == 1.0) return 7.0;
  return 0;
}

float getDurationVal(float val) {
  if (val == 1.0) return  1/64.0;
  if (val == 2.0) return 1/32.0;
  if (val == 3.0) return 1/16.0;
  if (val == 4.0) return 1/8.0;
  if (val == 5.0) return 1/4.0;
  if (val == 6.0) return 1/2.0;
  if (val == 7.0) return 1.0;
  return 0;
}

